#ifndef BBOX_H
#define BBOX_H

#include "Point3D.h"
#include "Triangle.h"
#include "Ray.h"

namespace geom
{
	/** An axis-aligned bounding box (bounding cube, really) class, designed to be used by an octree*/
	class BBox
	{
		protected:
			/** Helper function to assist in in/out tests */
			int btOutcode(const Vector3D& p,float halfExtent) const ;

		public:
			/** Center of the box */
			Point3D m_center;

			/** All sides of the box are equal to this value*/
			float m_side;

			/** Constructs a bbox
			  @param center Center of the BBox
			  @param side Length of every side of the BBox */
			BBox(Point3D center, float side);

			/** Default constructor. Creates a box of side 1 centered at the origin */
			BBox(){m_center = Point3D(0,0,0); m_side = 1;}

            /** Checks if this BBox contains the given point */
            bool contains(const Point3D &p) const;

            /** Checks if this BBox contains the given triangle */
            bool contains(const Triangle &t) const;
            
            /** Checks if this BBox is intersected by the given ray */
            bool isTraversedBy(const Ray &r) const;

			/** Checks if this BBox is intersected by the given ray, and returns the 
			  param value to compute the intersection point */
			bool getCollisionPoint(const Ray &ray, float &param) const;

			/** Adds one BBox to this one, resulting in a BBox that contains the original BBox and the added one */
			const BBox& operator+=(const BBox & other);

			/** Returns a BBox that encloses the original and the added boxes 
			 */
			const BBox operator+(const BBox & other);
	};

	/** An axis aligned bounding box class (this one really is a box, not a cube like the previous one */
	class FittedBBox
	{
		public:
			/** Constructor */
            FittedBBox(){}
			/** Constructor. Creates a BBox from a minpoint and a maxpoint 
			  @param minP Point of minimum coordinates of the box
			  @param maxP Point of maximum coordinates of the box
			  */
			FittedBBox(const geom::Point3D &minP, const geom::Point3D & maxP) : m_minP(minP), m_maxP(maxP){} 
			/** Returns the length of a given box side */
			float sideSize(int dimension){ return m_maxP.v[dimension] - m_minP.v[dimension];}
			/** Returns the minimum coordinate for a given axis */
			float minCoord(int dimension){ return m_minP.v[dimension];}
			/** Returns the maximum coordinate for a given axis */
			float maxCoord(int dimension){ return m_maxP.v[dimension];}
			/** Adds another box to this one, leaving this as one big enough to contain its former self
			  as well as the added box */
			const FittedBBox & operator+=(const FittedBBox &other)
			{
				for(int i=0;i<3;++i)
				{
					if(other.m_minP.v[i] < m_minP.v[i])
						m_minP.v[i] = other.m_minP.v[i];
					if(other.m_maxP.v[i] > m_maxP.v[i])
						m_maxP.v[i] = other.m_maxP.v[i];
				}
				return *this;
			}
			/** Adds another box and this one, returning a box big enough to contain both of them */
			FittedBBox operator+(const FittedBBox &other)
			{
				FittedBBox result(*this);
				result+=other;
				return result;
			}
		private:
			Point3D m_minP, m_maxP;

			friend class BSphere;
	};

	/** Bounding sphere class */
	class BSphere
	{
		public:
			/** Default constructor */
			BSphere() {}
			/** Constructs a bounding sphere of given radius, centered at the given point */
			BSphere(const Point3D & center, float radius) : m_center(center), m_radius(radius){}
			/** Creates a bounding sphere that completely surrounds the given bbox */
			BSphere(const FittedBBox & bbox)
			{
				m_center = (bbox.m_minP + bbox.m_maxP)/2.0;
				m_radius = m_center.distance(bbox.m_minP);
			}
			/** Sphere center */
			inline const Point3D & center() const{return m_center;}
			/** Sphere radius */
			inline float radius() const { return m_radius;}
			
			/** Gets this sphere after translating it and scaling by the given factors */
			BSphere translatedAndScaledBy(const geom::Point3D &p, float scale) const
			{
				BSphere result;
				result.m_center = m_center + p;
				result.m_radius = m_radius * scale;
				return result;
			}

		private:
			Point3D m_center;
			float m_radius; // square of the sphere radius
	};
}

#endif
